class Person {
    private name: string;
    private gender: string;
    private maritalStatus: string;
    public constructor(name: string, gender: string, maritalStatus: string) {
        this.name = name;
        this.gender = gender;
        this.maritalStatus = maritalStatus;
    }

    public getName(): string {
        return this.name;
    }
    public getGender(): string {
        return this.gender;
    }
    public getMaritalStatus(): string {
        return this.maritalStatus;
    }
}

interface Criteria {
    meetCriteria(people: Person[]): Person[];
}

class CriteriaMale implements Criteria {
    public meetCriteria(people: Person[]): Person[] {
        const males: Person[] = [];
        for (const person of people) {
            if (person.getGender().toUpperCase() === 'MALE') {
                males.push(person);
            }
        }
        return males;
    }
}

class CriteriaFemale implements Criteria {
    public meetCriteria(people: Person[]): Person[] {
        const females: Person[] = [];
        for (const person of people) {
            if (person.getGender().toUpperCase() === 'FEMALE') {
                females.push(person);
            }
        }
        return females;
    }
}

class CriteriaSingle implements Criteria {
    public meetCriteria(people: Person[]): Person[] {
        const singles: Person[] = [];
        for (const person of people) {
            if (person.getMaritalStatus().toUpperCase() === 'SINGLE') {
                singles.push(person);
            }
        }
        return singles;
    }
}

class AndCriteria implements Criteria {
    private criteria: Criteria;
    private otherCriteria: Criteria;
    public constructor(criteria: Criteria, otherCriteria: Criteria) {
        this.criteria = criteria;
        this.otherCriteria = otherCriteria;
    }
    public meetCriteria(people: Person[]): Person[] {
        const firstCriteria: Person[] = this.criteria.meetCriteria(people);
        const secondCriteria: Person[] = this.otherCriteria.meetCriteria(firstCriteria);
        return secondCriteria;
    }
}

class OrCriteria implements Criteria {
    private criteria: Criteria;
    private otherCriteria: Criteria;
    public constructor(criteria: Criteria, otherCriteria: Criteria) {
        this.criteria = criteria;
        this.otherCriteria = otherCriteria;
    }
    public meetCriteria(people: Person[]): Person[] {
        const firstCriteria: Person[] = this.criteria.meetCriteria(people);
        const secondCriteria: Person[] = this.otherCriteria.meetCriteria(people);
        for (const person of secondCriteria) {
            if (!firstCriteria.includes(person)) {
                firstCriteria.push(person);
            }
        }
        return firstCriteria;
    }
}

function main() {
    const people: Person[] = [
        new Person('John', 'Male', 'Single'),
        new Person('Jane', 'Female', 'Married'),
        new Person('Bob', 'Male', 'Single'),
        new Person('Alice', 'Female', 'Married'),
        new Person('Tom', 'Male', 'Single'),
        new Person('Lily', 'Female', 'Single')
    ];
    const males: Criteria = new CriteriaMale();
    console.log('Male People: ', males.meetCriteria(people));
    const females: Criteria = new CriteriaFemale();
    console.log('Female People: ', females.meetCriteria(people));
    const singles: Criteria = new CriteriaSingle();
    console.log('Single People: ', singles.meetCriteria(people));
    const maleSingle: Criteria = new AndCriteria(males, singles);
    console.log('Single and Male People: ', maleSingle.meetCriteria(people));
    const femaleOrSingle: Criteria = new OrCriteria(females, singles);
    console.log('Female Or Single People: ', femaleOrSingle.meetCriteria(people));
}

main();
